/*
 * Copyright 1999-2018 Alibaba Group Holding Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.alibaba.nacos.naming.remote.rpc.handler;

import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.remote.request.RequestMeta;
import com.alibaba.nacos.api.remote.response.Response;
import com.alibaba.nacos.api.remote.response.ResponseCode;
import com.alibaba.nacos.core.distributed.distro.DistroProtocol;
import com.alibaba.nacos.core.distributed.distro.entity.DistroData;
import com.alibaba.nacos.core.distributed.distro.entity.DistroKey;
import com.alibaba.nacos.core.distributed.distro.task.verify.DistroVerifyExecuteTask;
import com.alibaba.nacos.core.remote.RequestHandler;
import com.alibaba.nacos.naming.cluster.remote.request.DistroDataRequest;
import com.alibaba.nacos.naming.cluster.remote.response.DistroDataResponse;
import com.alibaba.nacos.naming.consistency.ephemeral.distro.v2.DistroClientDataProcessor;
import com.alibaba.nacos.naming.consistency.ephemeral.distro.v2.DistroClientTransportAgent;
import com.alibaba.nacos.naming.misc.Loggers;
import org.springframework.stereotype.Component;

/**
 * DistroDataRequestHandler 处理 DistroDataRequest
 * 这些方法都是委托 DistroProtocol 类来完成具体的调用
 *
 * Distro data request handler.
 *
 * @author xiweng.yy
 */
@Component
public class DistroDataRequestHandler extends RequestHandler<DistroDataRequest, DistroDataResponse> {
    
    private final DistroProtocol distroProtocol;
    
    public DistroDataRequestHandler(DistroProtocol distroProtocol) {
        this.distroProtocol = distroProtocol;
    }

    /**
     * DistroDataRequestHandler 处理 DistroDataRequest
     * 这些方法都是委托 DistroProtocol 类来完成具体的调用,用于实现 nacos 集群节点间的实例注册信息同步
     *
     * 比如说，服务实例注册，发起注册，注册到的是 nacos 集群中的节点1
     * 要订阅的服务，是注册在 nacos 集群中的节点2 中，那么此时就需要 nacos 集群节点中的，实例注册信息就需要同步，这样 不管实例注册在那台 nacos 节点，都能订阅到 其他实例
     * 问题：nacos 节点间的注册信息同步，是基于 distro 协议来同步的，
     *
     * @param request request
     * @param meta    request meta data
     * @return
     * @throws NacosException
     */
    @Override
    public DistroDataResponse handle(DistroDataRequest request, RequestMeta meta) throws NacosException {
        try {
            switch (request.getDataOperation()) {
                case VERIFY:
                    /**
                     *  处理 VERIFY 请求，仅实现了心跳的续约
                     *  对应 client 的 {@link DistroVerifyExecuteTask#run()}
                     */
                    return handleVerify(request.getDistroData(), meta);
                case SNAPSHOT:
                    /**
                     * 处理获得 当前节点实例注册信息快照，用于给当前 nacos 集群中的其他节点，同步注册信息
                     */
                    return handleSnapshot();
                case ADD:
                case CHANGE:
                case DELETE:
                    /**
                     * 同步节点发送过来的数据 处理
                     * 其中 DELETE 类型是单独的进行下线处理
                     */
                    return handleSyncData(request.getDistroData());
                case QUERY:
                    return handleQueryData(request.getDistroData());
                default:
                    return new DistroDataResponse();
            }
        } catch (Exception e) {
            Loggers.DISTRO.error("[DISTRO-FAILED] distro handle with exception", e);
            DistroDataResponse result = new DistroDataResponse();
            result.setErrorCode(ResponseCode.FAIL.getCode());
            result.setMessage("handle distro request with exception");
            return result;
        }
    }


    /**
     * 处理 VERIFY 请求
     *
     * @param distroData
     * @param meta
     * @return
     */
    private DistroDataResponse handleVerify(DistroData distroData, RequestMeta meta) {
        DistroDataResponse result = new DistroDataResponse();

        // 对于临时数据，最终会调用 EphemeralIpPortClientManager 的 verifyClient 方法
        if (!distroProtocol.onVerify(distroData, meta.getClientIp())) {
            /**
             * 验证不通过，返回错误码, 这个很重要
             * 对应 client 的 来处理响应：{@link DistroClientTransportAgent.DistroVerifyCallbackWrapper#onResponse(Response)}
             */
            result.setErrorInfo(ResponseCode.FAIL.getCode(), "[DISTRO-FAILED] distro data verify failed");
        }
        return result;
    }

    /**
     * 获得远程数据快照
     *
     * @return
     */
    private DistroDataResponse handleSnapshot() {
        DistroDataResponse result = new DistroDataResponse();
        /**
         * 获得当前节点的注册实例信息
         */
        DistroData distroData = distroProtocol.onSnapshot(DistroClientDataProcessor.TYPE);
        result.setDistroData(distroData);
        return result;
    }

    /**
     * 同步 nacos cluster中其他节点发送过来的 实例注册数据
     *
     * @param distroData
     * @return
     */
    private DistroDataResponse handleSyncData(DistroData distroData) {
        DistroDataResponse result = new DistroDataResponse();

        // 同步 nacos cluster中其他节点发送过来的 实例注册数据
        if (!distroProtocol.onReceive(distroData)) {
            result.setErrorCode(ResponseCode.FAIL.getCode());
            result.setMessage("[DISTRO-FAILED] distro data handle failed");
        }

        return result;
    }
    
    private DistroDataResponse handleQueryData(DistroData distroData) {
        DistroDataResponse result = new DistroDataResponse();
        DistroKey distroKey = distroData.getDistroKey();
        DistroData queryData = distroProtocol.onQuery(distroKey);
        result.setDistroData(queryData);
        return result;
    }
}
